Zurb foundation 5, Bower, Compass, Sinatra configuration - sinatra

I'm trying to add Zurb-foundation with sass into my sinatra app using the foundation-cli found here. I generated a foundation project and got all the files generated. I commented out the config.rb so I could configure Compass via the app.rb ie mvp.rb.
I'm having trouble linking to the actual assets found in the bower_components dir. I could link everything like so get 'bower_components/file' do ... end but I think you can use compass to automatically use the files based on the configuration. I'm not sure how to though.
In my haml views I have links to scripts that are not linked currently like %script{ src: 'js/jquery.js' }
config.rb
# Require any additional compass plugins here.
add_import_path "bower_components/foundation/scss"
# Set this to the root of your project when deployed:
# http_path = "/"
# css_dir = "stylesheets"
# sass_dir = "scss"
# images_dir = "images"
# javascripts_dir = "javascripts"
# You can select your preferred output style here (can be overridden via the command line):
# output_style = :expanded or :nested or :compact or :compressed
# To enable relative paths to assets via compass helper functions. Uncomment:
# relative_assets = true
# To disable debugging comments that display the original location of your selectors. Uncomment:
# line_comments = false
# If you prefer the indented syntax, you might want to regenerate this
# project again passing --syntax sass, or you can uncomment this:
# preferred_syntax = :sass
# and then run:
# sass-convert -R --from scss --to sass sass scss && rm -rf sass && mv scss sass
app directory
Gemfile config.rb public
Gemfile.lock index.html stylesheets
js views bower.json
mvp.rb bower_components
mvp.rb
1#!/usr/bin/env ruby
2# -*- coding: utf-8 -*-
3require 'rubygems'
4require 'bundler/setup'
5require 'sinatra'
6require 'haml'
7require 'compass'
8
9configure do
10 Compass.configuration do |config|
11 config.project_path = File.dirname(__FILE__)
12 config.sass_dir = File.join '/views/scss'
13 config.images_dir = 'views', 'images'
14 config.http_path = '/'
15 config.http_images_path = '/images'
16 config.http_stylesheets_path = '/stylesheets'
17 config.javascripts_dir = "js"
18 end
19
20 set :haml, { format: :html5 }
21 set :scss, { style: :compact, debug_info: false }
22end
23
24get '/stylesheets/:name.css' do |path|
25 content_type "text/css", charset: "utf-8"
26 scss :"scss/#{params[:name]}", Compass.sass_engine_options
27end

Related

Mediawiki with docker. Can't locate LocalSettings.php and cannot access database

I have setup a mediawiki (version 1.35.1) with a mariadb using docker on my Raspberry Pi3.
When I first run the docker-compose file everything works fine and I can create the LocalSettings.php.
Problem 1:
I copy the LocalSettings.php to the path /data/conf/LocalSettings.php, uncomment the line in the docker-compose.yml file and restart with docker-compose restart. But I always come back to the "Please complete the installation" page. It just works when i copy the file into the mediawiki container on path /var/www/html
Why is mediawiki not able to find the LocalSettings.php? Or am I understand something wrong?
Here is my docker-compose.yml file
version: '3.3'
services:
mediawiki:
image: mediawiki
restart: always
ports:
- ${BIND_TO}:${INT_PORT}
links:
- database
volumes:
- data_mw_images:/var/www/html/images
- ./data/sitemap:/var/www/html/sitemap
# After initial setup, download LocalSettings.php to data/conf directory
# and uncomment the following line and use compose to restart
# the mediawiki service
#- ./data/conf/LocalSettings.php:/var/www/html/LocalSettings.php:ro
# - ./data/conf/.htaccess:/var/www/html/.htaccess:ro
# Spezial Stuff (Google AdSense & Search Console)
#- ./data/conf/robots.txt:/var/www/html/robots.txt:ro
#- ./data/conf/ads.txt:/var/www/html/ads.txt:ro
# Mediwaiki Extensions
#- ./data/extensions/WikiCategoryTagCloud:/var/www/html/extensions/WikiCategoryTagCloud:ro
#- ./data/extensions/CategoryTagCloud:/var/www/html/extensions/CategoryTagCloud:ro
#- ./data/extensions/SelectCategory:/var/www/html/extensions/SelectCategory:ro
#- ./data/extensions/googleAnalytics:/var/www/html/extensions/googleAnalytics:ro
#- ./data/extensions/GoogleAdSense:/var/www/html/extensions/GoogleAdSense:ro
#- ./data/extensions/Lockdown:/var/www/html/extensions/Lockdown:ro
#- ./data/extensions/MobileFrontend:/var/www/html/extensions/MobileFrontend:ro
#- ./data/extensions/CookieWarning:/var/www/html/extensions/CookieWarning:ro
#- ./data/extensions/RelatedArticles:/var/www/html/extensions/RelatedArticles:ro
#- ./data/extensions/Description2:/var/www/html/extensions/Description2:ro
# Mediawiki Skins
#- ./data/skins/MinervaNeue:/var/www/html/skins/MinervaNeue:ro
database:
image: linuxserver/mariadb:arm32v6-latest
volumes:
- data_mw_db:/var/lib/mysql
restart: always
environment:
# #see https://phabricator.wikimedia.org/source/mediawiki/browse/master/includes/DefaultSettings.php
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
MYSQL_RANDOM_ROOT_PASSWORD: ${MYSQL_RANDOM_ROOT_PASSWORD}
TZ: ${TZ}
volumes:
data_mw_db:
data_mw_images:
Problem 2:
When I copy the LocalSettings.php into the container it works fine.
But when I restart my Pi and try to access the Mediawiki page i get another error (but the error appears not eveytime I restart. Sometimes it works... πŸ€¦β€β™‚οΈ)
Sorry! This site is experiencing technical difficulties.
Try waiting a few minutes and reloading.
(Cannot access the database)
Backtrace:
#0 /var/www/html/includes/libs/rdbms/loadbalancer/LoadBalancer.php(934): Wikimedia\Rdbms\LoadBalancer->reportConnectionError()
#1 /var/www/html/includes/libs/rdbms/loadbalancer/LoadBalancer.php(901): Wikimedia\Rdbms\LoadBalancer->getServerConnection(0, 'mywikidb', 0)
#2 /var/www/html/includes/libs/rdbms/loadbalancer/LoadBalancer.php(1046): Wikimedia\Rdbms\LoadBalancer->getConnection(-1, Array, 'mywikidb', 0)
#3 /var/www/html/includes/GlobalFunctions.php(2469): Wikimedia\Rdbms\LoadBalancer->getMaintenanceConnectionRef(-1, Array, 'mywikidb')
#4 /var/www/html/includes/cache/localisation/LCStoreDB.php(56): wfGetDB(-1)
#5 /var/www/html/includes/cache/localisation/LocalisationCache.php(449): LCStoreDB->get('en', 'deps')
#6 /var/www/html/includes/cache/localisation/LocalisationCache.php(495): LocalisationCache->isExpired('en')
#7 /var/www/html/includes/cache/localisation/LocalisationCache.php(414): LocalisationCache->initLanguage('en')
#8 /var/www/html/includes/cache/localisation/LocalisationCache.php(333): LocalisationCache->loadSubitem('en', 'messages', 'title-invalid-e...')
#9 /var/www/html/languages/Language.php(2645): LocalisationCache->getSubitem('en', 'messages', 'title-invalid-e...')
#10 /var/www/html/includes/cache/MessageCache.php(1030): Language->getMessage('title-invalid-e...')
#11 /var/www/html/includes/cache/MessageCache.php(988): MessageCache->getMessageForLang(Object(LanguageEn), 'title-invalid-e...', false, Array)
#12 /var/www/html/includes/cache/MessageCache.php(930): MessageCache->getMessageFromFallbackChain(Object(LanguageEn), 'title-invalid-e...', false)
#13 /var/www/html/includes/language/Message.php(1304): MessageCache->get('title-invalid-e...', false, Object(LanguageEn))
#14 /var/www/html/includes/language/Message.php(862): Message->fetchMessage()
#15 /var/www/html/includes/language/Message.php(954): Message->toString('text')
#16 /var/www/html/includes/title/MalformedTitleException.php(51): Message->text()
#17 /var/www/html/includes/title/MediaWikiTitleCodec.php(346): MalformedTitleException->__construct('title-invalid-e...', '')
#18 /var/www/html/includes/Title.php(3413): MediaWikiTitleCodec->splitTitleString('', 0)
#19 /var/www/html/includes/Title.php(427): Title->secureAndSplit('')
#20 /var/www/html/includes/MediaWiki.php(88): Title::newFromURL(NULL)
#21 /var/www/html/includes/MediaWiki.php(151): MediaWiki->parseTitle()
#22 /var/www/html/includes/MediaWiki.php(902): MediaWiki->getTitle()
#23 /var/www/html/includes/MediaWiki.php(543): MediaWiki->main()
#24 /var/www/html/index.php(53): MediaWiki->run()
#25 /var/www/html/index.php(46): wfIndexMain()
#26 {main}
Does anyone know how to fix that?
Here is the LocalSettings.php:
<?php
# This file was automatically generated by the MediaWiki 1.35.1
# installer. If you make manual changes, please keep track in case you
# need to recreate them later.
#
# See includes/DefaultSettings.php for all configurable settings
# and their default values, but don't forget to make changes in _this_
# file, not there.
#
# Further documentation for configuration settings may be found at:
# https://www.mediawiki.org/wiki/Manual:Configuration_settings
# Protect against web entry
if ( !defined( 'MEDIAWIKI' ) ) {
exit;
}
## Uncomment this to disable output compression
# $wgDisableOutputCompression = true;
$wgSitename = "Wiki Home";
$wgMetaNamespace = "Wiki_Home";
## The URL base path to the directory containing the wiki;
## defaults for all runtime URL paths are based off of this.
## For more information on customizing the URLs
## (like /w/index.php/Page_title to /wiki/Page_title) please see:
## https://www.mediawiki.org/wiki/Manual:Short_URL
$wgScriptPath = "";
## The protocol and server name to use in fully-qualified URLs
$wgServer = "http://192.168.178.22:9002";
## The URL path to static resources (images, scripts, etc.)
$wgResourceBasePath = $wgScriptPath;
## The URL paths to the logo. Make sure you change this from the default,
## or else you'll overwrite your logo when you upgrade!
$wgLogos = [ '1x' => "$wgResourceBasePath/resources/assets/wiki.png" ];
## UPO means: this is also a user preference option
$wgEnableEmail = true;
$wgEnableUserEmail = true; # UPO
$wgEmergencyContact = "apache#🌻.invalid";
$wgPasswordSender = "apache#🌻.invalid";
$wgEnotifUserTalk = false; # UPO
$wgEnotifWatchlist = false; # UPO
$wgEmailAuthentication = true;
## Database settings
$wgDBtype = "mysql";
$wgDBserver = "database";
$wgDBname = "wikidb";
$wgDBuser = "mediawikiuser";
$wgDBpassword = "xxx";
# MySQL specific settings
$wgDBprefix = "";
# MySQL table options to use during installation or update
$wgDBTableOptions = "ENGINE=InnoDB, DEFAULT CHARSET=binary";
# Shared database table
# This has no effect unless $wgSharedDB is also set.
$wgSharedTables[] = "actor";
## Shared memory settings
$wgMainCacheType = CACHE_ACCEL;
$wgMemCachedServers = [];
## To enable image uploads, make sure the 'images' directory
## is writable, then set this to true:
$wgEnableUploads = false;
$wgUseImageMagick = true;
$wgImageMagickConvertCommand = "/usr/bin/convert";
# InstantCommons allows wiki to use images from https://commons.wikimedia.org
$wgUseInstantCommons = false;
# Periodically send a pingback to https://www.mediawiki.org/ with basic data
# about this MediaWiki instance. The Wikimedia Foundation shares this data
# with MediaWiki developers to help guide future development efforts.
$wgPingback = true;
## If you use ImageMagick (or any other shell command) on a
## Linux server, this will need to be set to the name of an
## available UTF-8 locale. This should ideally be set to an English
## language locale so that the behaviour of C library functions will
## be consistent with typical installations. Use $wgLanguageCode to
## localise the wiki.
$wgShellLocale = "C.UTF-8";
## Set $wgCacheDirectory to a writable directory on the web server
## to make your wiki go slightly faster. The directory should not
## be publicly accessible from the web.
#$wgCacheDirectory = "$IP/cache";
# Site language code, should be one of the list in ./languages/data/Names.php
$wgLanguageCode = "en";
$wgSecretKey = "xxx";
# Changing this will log out all existing sessions.
$wgAuthenticationTokenVersion = "1";
# Site upgrade key. Must be set to a string (default provided) to turn on the
# web installer while LocalSettings.php is in place
$wgUpgradeKey = "cbbac7c99e42cd92";
## For attaching licensing metadata to pages, and displaying an
## appropriate copyright notice / icon. GNU Free Documentation
## License and Creative Commons licenses are supported so far.
$wgRightsPage = ""; # Set to the title of a wiki page that describes your license/copyright
$wgRightsUrl = "";
$wgRightsText = "";
$wgRightsIcon = "";
# Path to the GNU diff3 utility. Used for conflict resolution.
$wgDiff3 = "/usr/bin/diff3";
## Default skin: you can change the default skin. Use the internal symbolic
## names, ie 'vector', 'monobook':
$wgDefaultSkin = "vector";
# Enabled skins.
# The following skins were automatically enabled:
wfLoadSkin( 'MonoBook' );
wfLoadSkin( 'Timeless' );
wfLoadSkin( 'Vector' );
# End of automatically generated settings.
# Add more configuration options below.
$wgSessionCacheType = CACHE_DB;
Hope someone can help me.
Thanks and Regards

No grid/console views in buildbot, build view always empty, despite successful build

I have installed buildbot -- one docker image with a master, and another with a worker. Inter-container networking is allowed, and they share the same network; I have also a gitea instance, and installed the buildbot_gitea plugin.
So far I got a small project to run make on the worker after a push, and buildbot correctly reports success back to gitea (I can tell form the logs, and gitea also shows the green check image on the repo).
However,
the waterfall view is always empty; console and grid views do not load (they show the "loading" animation and never finish);
in the "Home" buildbot tab, sometimes the list of recent builds show up, sometimes it doesn't. (But the number of recent builds is always correct)
if I click on one of the builds (successful or not, doesn't matter), it shows a build page, but empty (no build steps, no build properties, nothing).
The only things that look strange on the master logs are periodic timeout messages, some connection drop messages:
2020-03-21 12:11:26+0000 [-] Timing out client: IPv4Address(type='TCP', host='172.27.0.1', port=56388)
2020-03-21 12:11:26+0000 [-] Timing out client: IPv4Address(type='TCP', host='172.27.0.1', port=56380)
2020-03-21 12:11:26+0000 [-] Timing out client: IPv4Address(type='TCP', host='172.27.0.1', port=56392)
2020-03-21 12:19:40+0000 [-] dropping connection to peer tcp4:172.27.0.1:56598 with abort=False: None
and this:
2020-03-21 12:10:49+0000 [-] Unhandled error in Deferred:
2020-03-21 12:10:49+0000 [-] Unhandled Error
Traceback (most recent call last):
File "/usr/lib/python3.8/threading.py", line 932, in _bootstrap_inner
self.run()
File "/usr/lib/python3.8/threading.py", line 870, in run
self._target(*self._args, **self._kwargs)
File "/bbot/sandbox/lib/python3.8/site-packages/twisted/_threads/_threadworker.py", line 46, in work
task()
File "/bbot/sandbox/lib/python3.8/site-packages/twisted/_threads/_team.py", line 190, in doWork
task()
--- <exception caught here> ---
File "/bbot/sandbox/lib/python3.8/site-packages/twisted/python/threadpool.py", line 250, in inContext
result = inContext.theWork()
File "/bbot/sandbox/lib/python3.8/site-packages/twisted/python/threadpool.py", line 266, in <lambda>
inContext.theWork = lambda: context.call(ctx, func, *args, **kw)
File "/bbot/sandbox/lib/python3.8/site-packages/twisted/python/context.py", line 122, in callWithContext
return self.currentContext().callWithContext(ctx, func, *args, **kw)
File "/bbot/sandbox/lib/python3.8/site-packages/twisted/python/context.py", line 85, in callWithContext
return func(*args,**kw)
File "/bbot/sandbox/lib/python3.8/site-packages/buildbot/buildbot_net_usage_data.py", line 204, in _sendBuildbotNetUsageData
res = _sendWithRequests(PHONE_HOME_URL, data)
File "/bbot/sandbox/lib/python3.8/site-packages/buildbot/buildbot_net_usage_data.py", line 197, in _sendWithRequests
r = requests.post(url, json=data)
File "/bbot/sandbox/lib/python3.8/site-packages/requests/api.py", line 119, in post
return request('post', url, data=data, json=json, **kwargs)
File "/bbot/sandbox/lib/python3.8/site-packages/requests/api.py", line 61, in request
return session.request(method=method, url=url, **kwargs)
File "/bbot/sandbox/lib/python3.8/site-packages/requests/sessions.py", line 530, in request
resp = self.send(prep, **send_kwargs)
File "/bbot/sandbox/lib/python3.8/site-packages/requests/sessions.py", line 643, in send
r = adapter.send(request, **kwargs)
File "/bbot/sandbox/lib/python3.8/site-packages/requests/adapters.py", line 516, in send
raise ConnectionError(e, request=request)
requests.exceptions.ConnectionError: HTTPSConnectionPool(host='events.buildbot.net', port=443): Max retries exceeded with url: /events/phone_home (Caused by NewConnectionError('<urllib3.connection.VerifiedHTTPSConnection object at 0x7ff7297704f0>: Failed to establish a new connection: [Errno 110] Operation timed out'))
which seems to happen only once (why is buildbot trying to phone home anyway? there is no mention of events.buildbot.net in any of my config files.
The docker containers have full network access, ipv6, routing and DNS are all fine (tested with the buildbot-master image).
This is my master.cfg:
import os
from twisted.application import service
from buildbot.master import BuildMaster
from buildbot.plugins import *
from buildbot_gitea.auth import GiteaAuth
from buildbot_gitea import *
basedir = '/bbot/bbot-master'
rotateLength = 10000000
maxRotatedFiles = 10
configfile = 'master.cfg'
umask = None
if basedir == '.':
basedir = os.path.abspath(os.path.dirname(__file__))
application = service.Application('buildmaster')
from twisted.python.logfile import LogFile
from twisted.python.log import ILogObserver, FileLogObserver
logfile = LogFile.fromFullPath(os.path.join(basedir, "twistd.log"), rotateLength=rotateLength,
maxRotatedFiles=maxRotatedFiles)
application.setComponent(ILogObserver, FileLogObserver(logfile).emit)
m = BuildMaster(basedir, configfile, umask)
m.setServiceParent(application)
m.log_rotation.rotateLength = rotateLength
m.log_rotation.maxRotatedFiles = maxRotatedFiles# -*- python -*-
# ex: set filetype=python:
from buildbot.plugins import *
c = BuildmasterConfig = {}
####### WORKERS
c['workers'] = [worker.Worker("bbot-worker", "BUILDBOT_PASSWORD")]
c['protocols'] = {'pb': {'port': 9989}}
####### CHANGESOURCES
c['change_source'] = []
c['change_source'].append(changes.PBChangeSource())
####### SCHEDULERS
c['schedulers'] = []
c['schedulers'].append(schedulers.SingleBranchScheduler(
name="all",
change_filter=util.ChangeFilter(branch='master'),
treeStableTimer=None,
builderNames=["runtests"]))
c['schedulers'].append(schedulers.ForceScheduler(
name="force",
builderNames=["runtests"]))
####### BUILDERS
factory = util.BuildFactory()
factory.addStep(steps.Gitea(repourl='gitea#gitea.mydomain:myself/repo.git',
mode='incremental',
workdir="build",
branch="master",
progress=True,
logEnviron=False,
))
factory.addStep(steps.ShellCommand(command=["make"]))
c['builders'] = []
c['builders'].append(
util.BuilderConfig(name="runtests",
workernames=["bbot-worker"],
factory=factory))
####### BUILDBOT SERVICES
c['services'] = [
reporters.GiteaStatusPush(
baseURL="https://gitea.mydomain/",
token="GITEA_API_ACCESS_TOKEN",
verbose=True)
]
####### PROJECT IDENTITY
c['title'] = "My Domain!"
c['titleURL'] = "https://buildbot.mydomain"
c['buildbotURL'] = "https://buildbot.mydomain/"
c['www'] = dict(port=8010,
plugins=dict(waterfall_view={}, console_view={}, grid_view={}))
c['www']['authz'] = util.Authz(
allowRules = [
util.AnyEndpointMatcher(role="admins")
],
roleMatchers = [
util.RolesFromUsername(roles=['admins'], usernames=['myself'])
]
)
c['www']['auth'] = GiteaAuth(
endpoint="https://gitea.mydomain/",
client_id="MY_CLIENT_ID_FROM_GITEA",
client_secret='MY_CLIENT_SECRET_FROM_GITEA')
c['www']['change_hook_dialects'] = {
'gitea': {
'secret': 'THE_GITEA_WEBHOOK_SECRET',
'onlyIncludePushCommit': True
}
}
####### DB URL
c['db'] = {
'db_url' : "postgresql://buildbot:MY_SECRET_DB_PASSWORD#172.25.0.2/buildbot",
}
The Dockerfile for the master is
FROM alpine:3.11.3
EXPOSE 9989
RUN apk update
RUN apk add python3 bash busybox-extras w3m gcc python3-dev libffi-dev openssl-dev musl-dev postgresql-dev
RUN mkdir /bbot
COPY entrypoint.sh /root/
RUN chmod a+x /root/entrypoint.sh
RUN mkdir /root/.ssh && chmod og-rwx /root/.ssh/
COPY bbot-gitea bbot-gitea.pub /root/.ssh/
RUN chmod og-w /root/.ssh/bbot-gitea*
RUN cd /bbot && \
python3 -m venv sandbox && \
source sandbox/bin/activate && \
pip3 install 'buildbot[bundle]' && \
pip3 install 'requests[security]' && \
pip3 -v install buildbot_gitea && \
pip3 install treq && \
pip3 install psycopg2
RUN apk del gcc python3-dev libffi-dev openssl-dev musl-dev
RUN ls -la /root
RUN cat /root/entrypoint.sh
ENTRYPOINT [ "/root/entrypoint.sh" ]
and the entrypoint does nothing special -- it is this,
#!/bin/bash
cd /bbot
echo " BBOT MASTER ENTRYPOINT"
source sandbox/bin/activate
buildbot upgrade-master bbot-master
# debug: check everything that was pip-installed:
echo "\n\n=====\n"
pip3 list
echo "=====\n\n"
if [ ! -f bbot-master/buildbot.tac ]; then
buildbot create-master bbot-master
fi
buildbot start bbot-master
tail -f /bbot/bbot-master/twistd.log
and the pip3 list line, which runs on startup for debugging, shows that I have
buildbot 2.7.0
buildbot-console-view 2.7.0
buildbot-gitea 1.2.0
buildbot-grid-view 2.7.0
buildbot-waterfall-view 2.7.0
buildbot-worker 2.7.0
buildbot-www 2.7.0
edit: checked the JS console in Firefox, and there seems to be a problem connecting to the server via websockets:
Firefox can’t establish a connection to the server at wss://buildbot.mydomain/ws.
From Chrome, this is what I see:
WebSocket connection to 'wss://buildbot.aleph0.info/ws' failed: Error during WebSocket handshake: Unexpected response code: 200
(200? why 200?)
I can't see why it wouldn't work. Apache is configured to do reverse proxying, like this:
RewriteEngine On
RewriteCond ${HTTP:Upgrade} websocket [NC]
RewriteCond ${HTTP:Connection} upgrade [NC]
RewriteRule .* "wss:/localhost:8010/$1" [P,L]
ProxyPass / http://localhost:8010/
ProxyPassReverse / http://localhost:8010/
So... What else can I do to continue debugging this?
(By the way, it does look like the buildbot mailing list is not very active -- after posting this question there I checked the archives, and there is svery low activity. Where do users of Buildbot go these days in order to get and share advice?)
I found the problem!
It was the reverse proxy that wasn't properly configured for websockets.
I used this in my apache virtualhost config,
<Location /ws>
ProxyPass ws://127.0.0.1:8010/ws
ProxyPassReverse ws://127.0.0.1:8010/ws
</Location>
ProxyPass /ws !
ProxyPass / http://localhost:8010/
ProxyPassReverse / http://localhost:8010/
and it works now!
( after searching a lot, I found the solution here:
https://docs.buildbot.net/0.9.2/manual/cfg-www.html )
There it is, in case anyone else needs it.

How are resources included in targets that are managed with Swift Package Manager 4? [duplicate]

I would like to ship my library using Apple's Swift Package Manager. However my lib includes a .bundle file with several strings translated in different languages.
Using cocoapods, I can include it using spec.resource. But in SwiftPM, I cannot do it. Any solution?
The package manager does not yet have any definition for how resources will be bundled with targets. We are aware of the need for this, but don't yet have a concrete proposal for it. I filed https://bugs.swift.org/browse/SR-2866 to ensure we have a bug tracking this.
Using Swift 5.3 it's finally possible to add localized resources πŸŽ‰
The Package initializer now has a defaultLocalization parameter which can be used for localization resources.
public init(
name: String,
defaultLocalization: LocalizationTag = nil, // New defaultLocalization parameter.
pkgConfig: String? = nil,
providers: [SystemPackageProvider]? = nil,
products: [Product] = [],
dependencies: [Dependency] = [],
targets: [Target] = [],
swiftLanguageVersions: [Int]? = nil,
cLanguageStandard: CLanguageStandard? = nil,
cxxLanguageStandard: CXXLanguageStandard? = nil
)
Let's say you have an Icon.png which you want to be localised for English and German speaking people.
The images should be included in Resources/en.lproj/Icon.png & Resources/de.lproj/Icon.png.
After you can reference them in your package like that:
let package = Package(
name: "BestPackage",
defaultLocalization: "en",
targets: [
.target(name: "BestTarget", resources: [
.process("Resources/Icon.png"),
])
]
)
Please note LocalizationTag is a wrapper of IETF Language Tag.
Credits and input from following proposals overview, please check it for more details.
starting on Swift 5.3, thanks to SE-0271, you can add bundle resources on swift package manager by adding resources on your .target declaration.
example:
.target(
name: "HelloWorldProgram",
dependencies: [],
resources: [.process(Images), .process("README.md")]
)
if you want to learn more, I have written an article on medium, discussing this topic
The solution I use for this is to build the data I need into a Swift object. To this end I have a shell script that will read an input file, base64 encode it, then write a Swift file that presents it as an InputStream. Then, when I want to add a data item to my Swift package, I run the script to read the file and write the output file. Of course the output file needs to be checked in so that the resource is available to those who use the project even if they do not have the script. (Typically I place my input files in a Resources directory and write the output to the Sources directory, but the script itself does not depend on that.)
I consider this a less than ideal solution, and am looking forward to when the package manager has this ability built in. But in the meantime, it is a workable solution.
The following example shows how it is used:
First, here is the script itself:
#!/usr/bin/env bash
# Read an input file, base64 encode it, then write an output swift file that will
# present it as an input stream.
#
# Usage: generate_resource_file.sh <inputfile> <outputfile> <streamName>
#
# The <streamName> is the name presented for the resulting InputStream. So, for example,
# generate_resource_file.sh Resources/logo.png Sources/Logo.swift logoInputStream
# will generate a file Sources/Logo.swift that will contain a computed variable
# that will look like the following:
# var logoInputStream: InputStream { ...blah...
#
set -e
if [ $# -ne 3 ]; then
echo "Usage: generate_resource_file.sh <inputfile> <outputfile> <streamName>"
exit -1
fi
inFile=$1
outFile=$2
streamName=$3
echo "Generating $outFile from $inFile"
echo "Stream name will be $streamName"
if [ ! -f "$inFile" ]; then
echo "Could not read $inFile"
exit -1
fi
echo "// This file is automatically generated by generate_resource_file.sh. DO NOT EDIT!" > "$outFile"
echo "" >> "$outFile"
echo "import Foundation" >> "$outFile"
echo "" >> "$outFile"
echo "fileprivate let encodedString = \"\"\"" >> "$outFile"
base64 -i "$inFile" >> "$outFile"
echo "\"\"\"" >> "$outFile"
echo "" >> "$outFile"
echo "var $streamName: InputStream {" >> "$outFile"
echo " get {" >> "$outFile"
echo " let decodedData = Data(base64Encoded: encodedString)!" >> "$outFile"
echo " return InputStream(data: decodedData)" >> "$outFile"
echo " }" >> "$outFile"
echo "}" >> "$outFile"
echo "Rebuilt $outFile"
Then, given an input file t.dat shown here:
Hello World!
Running the command generate_resource_file.sh t.dat HelloWorld.swift helloWorldInputStream generates the following HelloWorld.swift file:
// This file is automatically generated by generate_resource_file.sh. DO NOT EDIT!
import Foundation
fileprivate let encodedString = """
SGVsbG8gV29ybGQhCgo=
"""
var helloWorldInputStream: InputStream {
get {
let decodedData = Data(base64Encoded: encodedString)!
return InputStream(data: decodedData)
}
}
Due to framework bundles not being supported yet, the only way to provide bundle assets with an SPM target is through a Bundle. If you implement code in your framework to search for a particular bundle in your main project (supporting asset bundles), you can load resources from said bundle.
Example:
Access the bundled resources:
extension Bundle {
static func myResourceBundle() throws -> Bundle {
let bundles = Bundle.allBundles
let bundlePaths = bundles.compactMap { $0.resourceURL?.appendingPathComponent("MyAssetBundle", isDirectory: false).appendingPathExtension("bundle") }
guard let bundle = bundlePaths.compactMap({ Bundle(url: $0) }).first else {
throw NSError(domain: "com.myframework", code: 404, userInfo: [NSLocalizedDescriptionKey: "Missing resource bundle"])
}
return bundle
}
}
Utilize the Bundled resources:
let bundle = try! Bundle.myResourceBundle()
return UIColor(named: "myColor", in: bundle, compatibleWith: nil)!
You can apply the same logic for all resource files, including but not limited to storyboards, xibs, images, colors, data blobs, and files of various extensions (json, txt, etc).
Note: Sometimes this makes sense, sometimes it doesn't. Determine use to own project's discretion. It would take very specific scenarios to justify separating Storyboards/Xibs into bundled assets.
Important note:
Resources doesn't seem to be included in the Xcode project generated by
swift package generate-xcodeproj
But they are when you open the Package folder on Xcode (xed .) and then double click on the package to resolve dependencies.
I'm including a nice tutorial as well: https://medium.com/better-programming/how-to-add-resources-in-swift-package-manager-c437d44ec593

How to include assets / resources in a Swift Package Manager library?

I would like to ship my library using Apple's Swift Package Manager. However my lib includes a .bundle file with several strings translated in different languages.
Using cocoapods, I can include it using spec.resource. But in SwiftPM, I cannot do it. Any solution?
The package manager does not yet have any definition for how resources will be bundled with targets. We are aware of the need for this, but don't yet have a concrete proposal for it. I filed https://bugs.swift.org/browse/SR-2866 to ensure we have a bug tracking this.
Using Swift 5.3 it's finally possible to add localized resources πŸŽ‰
The Package initializer now has a defaultLocalization parameter which can be used for localization resources.
public init(
name: String,
defaultLocalization: LocalizationTag = nil, // New defaultLocalization parameter.
pkgConfig: String? = nil,
providers: [SystemPackageProvider]? = nil,
products: [Product] = [],
dependencies: [Dependency] = [],
targets: [Target] = [],
swiftLanguageVersions: [Int]? = nil,
cLanguageStandard: CLanguageStandard? = nil,
cxxLanguageStandard: CXXLanguageStandard? = nil
)
Let's say you have an Icon.png which you want to be localised for English and German speaking people.
The images should be included in Resources/en.lproj/Icon.png & Resources/de.lproj/Icon.png.
After you can reference them in your package like that:
let package = Package(
name: "BestPackage",
defaultLocalization: "en",
targets: [
.target(name: "BestTarget", resources: [
.process("Resources/Icon.png"),
])
]
)
Please note LocalizationTag is a wrapper of IETF Language Tag.
Credits and input from following proposals overview, please check it for more details.
starting on Swift 5.3, thanks to SE-0271, you can add bundle resources on swift package manager by adding resources on your .target declaration.
example:
.target(
name: "HelloWorldProgram",
dependencies: [],
resources: [.process(Images), .process("README.md")]
)
if you want to learn more, I have written an article on medium, discussing this topic
The solution I use for this is to build the data I need into a Swift object. To this end I have a shell script that will read an input file, base64 encode it, then write a Swift file that presents it as an InputStream. Then, when I want to add a data item to my Swift package, I run the script to read the file and write the output file. Of course the output file needs to be checked in so that the resource is available to those who use the project even if they do not have the script. (Typically I place my input files in a Resources directory and write the output to the Sources directory, but the script itself does not depend on that.)
I consider this a less than ideal solution, and am looking forward to when the package manager has this ability built in. But in the meantime, it is a workable solution.
The following example shows how it is used:
First, here is the script itself:
#!/usr/bin/env bash
# Read an input file, base64 encode it, then write an output swift file that will
# present it as an input stream.
#
# Usage: generate_resource_file.sh <inputfile> <outputfile> <streamName>
#
# The <streamName> is the name presented for the resulting InputStream. So, for example,
# generate_resource_file.sh Resources/logo.png Sources/Logo.swift logoInputStream
# will generate a file Sources/Logo.swift that will contain a computed variable
# that will look like the following:
# var logoInputStream: InputStream { ...blah...
#
set -e
if [ $# -ne 3 ]; then
echo "Usage: generate_resource_file.sh <inputfile> <outputfile> <streamName>"
exit -1
fi
inFile=$1
outFile=$2
streamName=$3
echo "Generating $outFile from $inFile"
echo "Stream name will be $streamName"
if [ ! -f "$inFile" ]; then
echo "Could not read $inFile"
exit -1
fi
echo "// This file is automatically generated by generate_resource_file.sh. DO NOT EDIT!" > "$outFile"
echo "" >> "$outFile"
echo "import Foundation" >> "$outFile"
echo "" >> "$outFile"
echo "fileprivate let encodedString = \"\"\"" >> "$outFile"
base64 -i "$inFile" >> "$outFile"
echo "\"\"\"" >> "$outFile"
echo "" >> "$outFile"
echo "var $streamName: InputStream {" >> "$outFile"
echo " get {" >> "$outFile"
echo " let decodedData = Data(base64Encoded: encodedString)!" >> "$outFile"
echo " return InputStream(data: decodedData)" >> "$outFile"
echo " }" >> "$outFile"
echo "}" >> "$outFile"
echo "Rebuilt $outFile"
Then, given an input file t.dat shown here:
Hello World!
Running the command generate_resource_file.sh t.dat HelloWorld.swift helloWorldInputStream generates the following HelloWorld.swift file:
// This file is automatically generated by generate_resource_file.sh. DO NOT EDIT!
import Foundation
fileprivate let encodedString = """
SGVsbG8gV29ybGQhCgo=
"""
var helloWorldInputStream: InputStream {
get {
let decodedData = Data(base64Encoded: encodedString)!
return InputStream(data: decodedData)
}
}
Due to framework bundles not being supported yet, the only way to provide bundle assets with an SPM target is through a Bundle. If you implement code in your framework to search for a particular bundle in your main project (supporting asset bundles), you can load resources from said bundle.
Example:
Access the bundled resources:
extension Bundle {
static func myResourceBundle() throws -> Bundle {
let bundles = Bundle.allBundles
let bundlePaths = bundles.compactMap { $0.resourceURL?.appendingPathComponent("MyAssetBundle", isDirectory: false).appendingPathExtension("bundle") }
guard let bundle = bundlePaths.compactMap({ Bundle(url: $0) }).first else {
throw NSError(domain: "com.myframework", code: 404, userInfo: [NSLocalizedDescriptionKey: "Missing resource bundle"])
}
return bundle
}
}
Utilize the Bundled resources:
let bundle = try! Bundle.myResourceBundle()
return UIColor(named: "myColor", in: bundle, compatibleWith: nil)!
You can apply the same logic for all resource files, including but not limited to storyboards, xibs, images, colors, data blobs, and files of various extensions (json, txt, etc).
Note: Sometimes this makes sense, sometimes it doesn't. Determine use to own project's discretion. It would take very specific scenarios to justify separating Storyboards/Xibs into bundled assets.
Important note:
Resources doesn't seem to be included in the Xcode project generated by
swift package generate-xcodeproj
But they are when you open the Package folder on Xcode (xed .) and then double click on the package to resolve dependencies.
I'm including a nice tutorial as well: https://medium.com/better-programming/how-to-add-resources-in-swift-package-manager-c437d44ec593

Sinatra, Compass + Asset Pack

So I'm trying to use Compass and Asset Pack in a Sinatra app. My setup is the following:
require 'sinatra'
require 'bundler'
require 'sinatra/assetpack'
require 'sinatra/support'
require 'compass'
class Application < Sinatra::Base
register Sinatra::AssetPack
register Sinatra::CompassSupport
register Sinatra::SimpleNavigation
set :static, true
set :root, File.dirname(__FILE__)
set :public_folder, File.dirname(__FILE__) + '/public'
set :scss, Compass.sass_engine_options
set :scss, { :load_paths => [ "#{Application.root}/assets/css" ] }
Compass.configuration do |config|
config.sass_dir = "assets/css"
config.project_path = root
config.images_dir = "assets/images"
config.http_generated_images_path = "/images"
config.fonts_dir = "assets/fonts"
end
assets {
serve '/fonts', from: 'assets/fonts'
serve '/js', from: 'assets/js' # Default
serve '/css', from: 'assets/css' # Default
serve '/images', from: 'assets/images' # Default
# The second parameter defines where the compressed version will be served.
# (Note: that parameter is optional, AssetPack will figure it out.)
js :app, '/js/app.js', [
'/js/*',
]
css :application, '/css/application.css', [
'/css/style.css'
]
js_compression :jsmin # :jsmin | :yui | :closure | :uglify
css_compression :sass # :simple | :sass | :yui | :sqwish
}
# Routes and things here
end
With the line set :scss, Compass.sass_engine_options it seems to enable Compass, but this over rides the next line and I then can't use SCSS partials because it doesn't know the load path. Having them on the same line has the same effect.
TL:DR: I can't get Compass and SCSS Partials to work together with the Asset Pack
Try merging the :scss options? It looks like you're overriding with your second set call:
set :scss, Compass.sass_engine_options.merge({ :load_paths => [ "#{Application.root}/assets/css" ] })
Also, the Compass configuration and set calls should be inside a configure block.