Create a dynamic preprocessor macro in Xcode - ios

I often work on iOS projects alongside a development server, which is always running on the same machine that I'm using to build the app. A typical scenario is that I have a rails app running on http://localhost:3000 and I'm testing on the device. In order to test on the device, I need a resolved IP address so that my app can talk to my development server on the local network.
I know that I can use GCC_PREPROCESSOR_DEFINITIONS to set some environment variable into a preprocessor macro, and I know that I can get the build machine's IP address by running ifconfig | grep -E 'inet.[0-9]' | grep -v '127.0.0.1' | awk '{ print $2}' in a bash script.
What I want to do is define MY_SERVER_URL in GCC_PREPROCESSOR_DEFINITIONS using a value such as '#"http://${BUILD_MACHINE_IP_ADDRESS}:3000"', but what I can't figure out is how to set ${BUILD_MACHINE_IP_ADDRESS} using the ifconfig script above.
I have considered editing a configuration file using a run script build phase, but the ramifications of a change like this are irritating. For example, I wouldn't want the file included in source control because my IP address changes frequently and it would cause conflicts. But I would need it to be included in the project file so that the application can access it. This would lead to a file that's required for the project to build but isn't included in the source control, which is ugly.
I've also tried running export BUILD_MACHINE_IP_ADDRESS=`ifconfig | grep -E 'inet.[0-9]' | grep -v '127.0.0.1' | awk '{ print $2}` in a run script build phase but that doesn't yield a result.

One way of doing this is to have a run-script build phase which writes a .h file that #defines the macro and is included your sources.
I don't believe that writing a .xcconfig during build will have any effect on that build.
Exporting environment variables from run-script build phases won't work for multiple reasons. First of all, environment variables may be created from build settings but don't work the other way around. Second, even if they did, a child process can't set environment variables in its parent.
For another approach entirely, have you looked into using the .local host name of your development machine instead of a numeric IP address? Check the Sharing pane of System Preferences, which will show you the domain name for your system.

Related

Alternative to using --squash when building docker images on Windows using local files

We have some local installers and zip files that we use to build our docker images. It is easy to get this to work in a Dockerfile:
FROM mcr.microsoft.com/windows/nanoserver
COPY myinstaller.exe .
RUN myinstaller.exe; \
del myinstaller.exe
The problem here is that it produces a layer for the COPY line, which increases the size of the image. A common work-around for this is to have one RUN line, that downloads the file from the Internet, runs commands, and then deletes the installation file. The problem, as written above, is that the installers are on the local filesystem.
I found that there is a --squash command for docker:
docker build --squash -t mytestimage .
This does exactly what I want: It gives me an image without this extra installer file that is not necessary. To run this command, you need to enable experimental features though. There is also an open issue to simply remove this feature:
https://github.com/moby/moby/issues/34565
Is there some alternative way of using local installers in a Dockerfile when running on Windows, that doesn't involve setting up a server to provide the files?
We ended up setting up nginx to provide files when building. On our build server, the machine building our docker images and the server that has the installer files have a very good connection between them, so downloading huge files is not a real problem.
When it comes to --squash, it is bugged for Docker on Windows. Here is the relevant issue for it:
https://github.com/moby/moby/issues/31468
There is an issue to move --squash out of experimental, but it doesn't seem to have a lot of support:
https://github.com/moby/moby/issues/38657
The alternative that some people propose instead of --squash is multi stage build, discussion here:
https://github.com/moby/moby/issues/34565
There is an alternative to --squash, if you have local installer files, you don't want to set up a web server, and you would like your docker image to be small, and you are running Windows: Use mapped drives.
In Windows, you can share folders with other users on your network. Docker containers are like another computer that is running on your physical machine, and it can access these network drives.
First set up a new user, for example username share and password password1. Create a folder somewhere on your computer. Then right click it, click properties, and then go to the Sharing tab and click "Share". Find the user that you have just created, using the little dropdown menu and Find people ..., and share the folder with this user.
Create a folder somewhere for your test project. Create a batch file setupshare.bat that looks like this:
#echo off
for /f "tokens=2 delims=:" %%i in ('ipconfig ^| findstr "Default Gateway"') do (
set hostip=%%i
goto :end
)
:end
set hostip=%hostip: =%
net use O: \\%hostip%\vms /USER:share password1
The first part of this file is only to find the ip address that the docker container can use to access its host computer. It is not the most pretty thing I've ever put together, so let me know if there's a better way!
It uses a for-loop, as that is the way to save the output of a command to a variable in batch files. The command is ipconfig, and we pipe it to findstr and searches for Default Gateway. We need to use ^| instead of just | because it is in a for-loop. The first part of the for-loop divides each line from the command on the delimiter, which is : in this case, and we only take the second token. The for-loop only handles the first line, if there are multiple entries with a Default Gateway. This script doesn't work if there are multiple entries and the first one is not the correct one.
The line set hostip=%hostip: =% is to remove a space at the start of the string.
We then have the IP address that we want to use stored in hostip. We use this in the net use command, which will map O:\ to shared folder vms on the machine with IP hostip. We use the username share and the password password1. Note that this is a very bad way of handling passwords, as they kind of should be secret!
With a batch file like this, we can set up a Dockerfile in this way:
# escape=`
FROM mcr.microsoft.com/dotnet/core/sdk:3.0
COPY setupshare.bat .
RUN setupshare.bat && `
copy O:\file.txt file.txt
The RUN command will first call setupshare.bat that sets up the network share properly. We can then use any file that we shared, for example a huge installer, and install the things that we want. In this case I have only shared a test file file.txt to see that it works, so just change that line.
I would still advice everyone to just set up a little web server, for example nginx, and use the standard way of writing Dockerfiles, with downloading files and running it in the same RUN command. That's what people expect when they see a Dockerfile, and it should be a more robust solution.
We can also hope that the Docker people either makes a COPY command that can copy, run, and delete installers in the same layer, or that --squash is implemented properly.

Need to know how to use Groovy to automate a Docker build & runtime

I have a task to containerize a Spring & React web-app so that non-technical staff can make use of the container to demo the app to clients. Currently we develop on OSX & deploy to Tomcat on AWS managed by a 3rd party firm, and the non-technical staff use Windows laptops for their stuff.
So far I have bash scripts in OSX which will create a Packager container that has a Java 8 SDK & maven installed, & which will compile the app into a war file. A second script creates and initializes a mongodb container & gives it a name, and the third script creates a Tomcat/Java 8 container, loads the war file into it, links it to the mongodb container & sets it running. In bash on OSX this works fine, but I found it didn't work if I tried it in cygwin on Windows 10, and my CMD/Powershell-fu is too weak to script it in a Windows native fashion.
So, I'm trying to do the script in something that'll run on both OSX, an AWS linux server & Windows 10, & being a Java developer myself I thought of Groovy. This is my first time scripting Docker using Groovy so I've ended up resorting to structures like:
println "docker build -f Dockerfile.packager -t mycontainer .".execute().text
I wonder if Docker has a Java or Groovy API that I could plug into & do things like:
docker.build("Dockerfile.packager").tag("mycontainer")
Currently my script is determining the location of the project root & building up the Docker run command as a string, like:
File emToo = new File(System.getProperty("user.dir")+"/.m2")
String currentDirectory = new File(".").getCanonicalPath()
String projectRoot = new File(currentDirectory+"/../").getCanonicalPath()
I get an option string from the user via a command line prompt, "Do you want QA or Dev?" & then:
String dockerRunCmd = "docker run -it -v $projectRoot/:/usr/local/build/myproject:cached -v ${emToo.getCanonicalPath()}:/root/.m2:cached mycontainer $option"
println dockerRunCmd.execute().text
Currently it doesn't seem to do anything after asking for the option - it's kinda bombing out. I get the run command output to screen, & if I copy/paste that into a command line in the scripts directory it falls over saying that the parent pom can't be found. Remember though that if I run the OSX bash script to do this, it works just fine. The bash script is basically:
#! /usr/bin/env bash
CWD=`pwd`
options=$1
docker run -it -v $CWD/../:/usr/local/build/myproject:cached -v ~/.m2:/root/.m2:cached --rm mycontainer $options
...which I think amounts to the same thing, right? Where's it going wrong?
UPDATE: I've found a bug - I should have been setting emToo to
new File(System.getProperty("user.home")+"/.m2"). user.dir just picks up the current directory, & the maven .m2 directory is in the user's home, usually. Currently though, the script gives me a run command that works if I cut/paste into a command line, but which doesn't allow me to call .execute() on the string in Groovy. If I can get that to work, there'll be no need for the docker-client projects suggested.
There are different ways to communicate with docker from groovy or java (sdk's are listed there https://docs.docker.com/engine/api/sdks/#other-languages):
Groovy (https://github.com/gesellix/docker-client)
Java (https://github.com/docker-java/docker-java)
Many others can be also found on github.
But as I see you are using maven so probably it will be easier for you to use awesome docker maven plugin (https://dmp.fabric8.io) which can build, push images, run containers etc.

Override rpmbuild build location when called from command line

I am making some config packages which are built by Jenkins, then checked out whenever they are needed. The package itself is built and runs fine. My problem right now is the directories that rpmbuild uses for actually building the project. When I call rpmbuild SPECS/package.spec from my working directory, rpmbuild makes a new directory at /home/user/rpmbuild. This was fine when I was running tests but I would rather that I just be able to build from whatever file it is called from for the Jenkins process.
I see online people saying to make a ~/.rpmmacros file to overwrite the $_topdir variable. That approach isn't really working for the Jenkins build. Is there some way to simply call rpmbuild and build in the current directory? The structure is all there and it would work better for what I am trying to do. Thanks.
Yes, just override _topdir directly.
rpmbuild -D '_topdir /new/value/for/_topdir'
or
rpmbuild --define='_topdir /new/value/for/_topdir'
those should be identical but I've learned that they aren't always for some reason (and in quick tests rpm -D '_topdir /opt/tnstmp' --showrc | grep _topdir doesn't show the modified value but rpm --define '_topdir /opt/tnstmp' --showrc | grep _topdir did).

How to easily configure multiple u-boot variables

I need to change multiple u-boot environment variables every time I setup a new embedded device e.g. ip address, ethernet address, etc.
Typing at the terminal prompt is tedious, and, I don't know if it's my terminal, buy trying to cut and paste anything more than a few characters can result in errors. Changing them in a text editor and copying that file to a specific location in flash would be much better than the terminal.
Anyone have a good way to change multiple environment variables at once?
A script file is ideal for this situation. Its much better than copy & pasting for many commands and can handle much more complexity. You can enter all your commands into a text file and create a script image using mkimage (where myscript is the text file's name):
mkimage -T script -C none -n 'My Script' -d myscript myscript.img
Then you can simply load and execute myscript.img to perform all setup tasks for the device.
For example, to load and execute myscript.img from USB stick:
usb start && load usb 0:1 ${loadaddr} myscript.img && source ${loadaddr}
It is possible to add this load command to U-Boot's default environment so all you need to do is run the name of the command. You can even add logic to the default boot sequence to automatically perform the device setup if the USB device and script file are present. Depending on the U-Boot version, you can manipulate the default environment by either modifying the U-Boot source or by editing uEnv.txt (when supported).
Scripts are also useful for maintaining multiple setup configurations that would allow you to set the device up for one of many deployment or development configurations.
I have been able to use PuTTY to copy and paste my variables into U-Boot. You can separate the declarations by semicolons to if you want to do all of the variables at once, like this:
setenv ipaddr 192.168.1.5; setenv serverip 192.168.1.10;

Register for messages from collabd like XCSBuildService to receive Xcode Bots integration number

During an Xcode Bots build each run gets an integration number assigned. This number does not show in the build logs but would be convenient to create http links back to the individual Xcode Bots build in an CI environment or enterprise app store.
In the /Library/Server/Xcode/Logs/xcsbuildd.log I found that XCSBuildService gets a message/event from collabd with a structure that contains the integration number.
Is there a way to register to the same event in an own (OSX) program and receive this message/event?
The only ugly way I found so far to get the Xcode Bots integration number was by parsing the xcsbuildd.log file with the drawback that I can't be sure that the latest integration number corresponds with my current build when several builds are executing in parallel.
This log file is also located in an read protected folder/file so that I have to either change the permissions (ugh!) or use sudo (I don't really want to do that)!?
Example:
sudo grep -r "integration =" /Library/Server/Xcode/Logs/xcsbuildd.log | tail -1 | cut -d'=' -f 2| cut -d';' -f 1 |tr -d '\040\011\012\015'
Gives me the latest integration number stripped from whitespace ...
Edit:
Just found out that if you actually include the following script in your scheme as Build-post-action it will create a file (e.g. /Library/Server/Xcode/Data/BotRuns/Cache/22016b1e-2f91-4757-b3b8-322233e87587/source/integration_number.txt) with the integration number without requiring sudo. Xcode Bots seems to serialize the different builds so that they are not executed in parallel and so the created integration number in the file could be used.
grep -r "integration =" /Library/Server/Xcode/Logs/xcsbuildd.log | tail -1 | cut -d'=' -f 2| cut -d';' -f 1 |tr -d '\040\011\012\015' > ${PROJECT_DIR}/integration_number.txt
For specifically the case of getting the integration build number, I've added a simple answer here.
To repeat myself (and keep this from being down voted as a 1-line answer):
I implemented this using a Shell Script Build Phase in my Xcode project. In my case, I used the integration number to set the internal version of my built product. My script looks like this:
if [ "the$XCS_INTEGRATION_NUMBER" == "the" ]; then
echo "Not an integration build…"
xcrun agvtool new-version "10.13"
else
echo "Setting integration build number: $XCS_INTEGRATION_NUMBER"
xcrun agvtool new-version "$XCS_INTEGRATION_NUMBER"
fi
Note that XCS_INTEGRATION_NUMBER exists by default in the Xcode Server build environment. If you want to simulate an integration build (for the purposes of this script), you can simply add it to your build settings as a custom variable.

Resources