Pull to refresh

Как я стенд для сборки с windows на wine мигрировал

Reading time9 min
Views13K
image

Преамбула


Есть у меня несколько старых проектов, писанных на С++, которые все еще развиваю по мере сил. Казалось бы — в чем же дело? Увы, это пачка очередных плагинов под мой любимый Adobe InDesign.

И каждый раз, когда выходит новый Creative Suite, приходится портировать это дело. Что интересно, основные усилия уходят на то, чтобы собрать новую версию по новым правилам, и подогнуть инсталлятор. Потому как уж если дошел до стадии «оно компилируется», то как правило — работает. Хотя конечно есть нюансы — например в один прекрасный момент PlaceGun перестал раскладывать несколько выбранных изображений, только первое. Но об этом — в следующий раз.

И разумеется — хотелось бы это собирать под все версии и все платформы за раз, а не «открыл вижлу — собрал — закрып — повторил».

Итак, для сборки, нам нужны одновременно

  • MS VS 2005
  • MS VS 2005 sp1
  • MS VS 2008
  • MS VS 2010
  • MS VS 2012




image

Переходим на gmake — профит


Почему так? Ах, я не сказал — еще есть MacOSX. Так что прощай nmake без вариантов.

И в итоге получаем вот такой кошмар:
Скрытый текст
  1. cl-cs3.mak
  2. cl-cs4.mak
  3. cl-cs5.mak
  4. cl-cs55.mak
  5. cl-cs6.mak
  6. cl-cc.mak
  7. cl-cc2014.mak
  8. cl.mak
  9. commplugs.mak
  10. gcc-cs3.mak
  11. gcc-cs4.mak
  12. gcc-cs5.mak
  13. gcc-cs55.mak
  14. gcc-cs6.mak
  15. gcc-cc.mak
  16. gcc-cc2014.mak
  17. gcc.mak
  18. mac-defs.mak
  19. platform-impl.mak
  20. platform-targets.mak
  21. platform.mak
  22. win-defs.mak



А запускается это примерно так:
make ARCH=x64 INDD=cc2014 compile


Что очевидно, платформа определяется через uname. А каждая часть собирается стандартным образом как make -C foo.

Описание каждого компонента выглядит примерно так
Скрытый текст
include ../../make/platform.mak

TARGET=../../../lib/$(ARCH)/$(INDD)/libz.$(LibSuffix)

OBJS=\
    $(ARCH)/$(INDD)/adler32.$(OBJSuffix) \
    $(ARCH)/$(INDD)/compress.$(OBJSuffix) \
    $(ARCH)/$(INDD)/crc32.$(OBJSuffix) \
    $(ARCH)/$(INDD)/deflate.$(OBJSuffix) \
    $(ARCH)/$(INDD)/gzio.$(OBJSuffix) \
    $(ARCH)/$(INDD)/infback.$(OBJSuffix) \
    $(ARCH)/$(INDD)/inffast.$(OBJSuffix) \
    $(ARCH)/$(INDD)/inflate.$(OBJSuffix) \
    $(ARCH)/$(INDD)/inftrees.$(OBJSuffix) \
    $(ARCH)/$(INDD)/trees.$(OBJSuffix) \
    $(ARCH)/$(INDD)/uncompr.$(OBJSuffix) \
    $(ARCH)/$(INDD)/zutil.$(OBJSuffix) \

all: $(TARGET)

$(TARGET): $(OBJS)
	 $(AR) $(LFLAGS) $(OBJS)

clean:
 	$(RMRF) $(TARGET) $(OBJS)


И все это работало на отдельно выделенной виртуалке под WinXP (и такой же под хакинтошем).

Расписывать эти страсти от и до смысла как бы не вижу, приведу только наиболее интересные выдержки. Например, вот так вычисляем project root и платформу, на которой сейчас будет выполняться компиляция:

PR:=$(subst make/,,$(dir $(CURDIR)/$(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))))
OSA:=$(shell uname -o)

ifeq (Darwin,$(OSA))
OS=mac
else
OS=win
endif


А вот так определяется где искать Boost (адобы в каждой версии умудряются переложить, поэтому тут две части — определяем макрос и потом его используем):
ifeq ($(ARCH),x86)
BoostARCH=32
else
BoostARCH=64
endif
BoostLib=$(subst \,/,$(AdobeSDK))/external/dva/third_party/boost_libraries/bin.v2/libs/boost_$1/lib/$(OS)/release/$(BoostARCH)/boost_$1.$(LibSuffix)

BoostFilesystemLib=$(call BoostLib,filesystem)
BoostThreadLib=$(call BoostLib,threads)
BoostRegexLib=$(call BoostLib,regex)
BoostSystemLib=$(call BoostLib,system)


Понятно, что набирал я это не руками, а написал пачку батников, да под MinGW:

genmake-for-dll.bat component-name [path-to-sources [target-directory]]
@echo off
rem genmake component-name [path-to-sources [target-directory]]
set CN=%1
set FP=%2
if -%1==- set CN=default
if -%2==- set FP=.
if -%3==- set TD=.
echo>  MakeF COMPONENT=%CN%
echo>> MakeF include ../make/platform.mak
echo>> MakeF TARGET=$(OBJDIR)/$(COMPONENT).$(DLLSuffix)
echo>> MakeF OBJS=\
find %FP% -type f -name '*.c*' | grep -v .svn | awk '{ print "\t$(OBJDIR)/" $1 ".$(OBJSuffix)\t\\\"; }' | sed -e 's/\.cpp\./\./' | sed -e 's/\.cxx\./\./' | sed -e 's?/%FP%??' >> MakeF
echo>> MakeF #
echo>> MakeF HEADERS=\
find %FP% -type f -name '*.h*' | grep -v .svn | awk '{ print "\t" $1 "\t\\\"; }' > headers  >> MakeF
echo>> MakeF #
echo>> MakeF #
echo>> MakeF all: $(TARGET)
echo>> MakeF #
echo>> MakeF $(TARGET): $(OBJS)
echo>> MakeF 	$(LINK) $(LDFLAGS) $(OBJS) $(XLIBS)
echo>> MakeF	if [ -f $@.manifest ] ; then mt -nologo -manifest $@.manifest "-outputresource:$@;2"; fi
echo>> MakeF #
echo>> MakeF clean:
echo>> MakeF 	rm -f $(TARGET) $(OBJS)
echo>> MakeF #
find %FP% -type f -name '*.c*' | grep -v .svn | awk '{ print "$(OBJDIR)/" $1 ".$(OBJSuffix) : " $1 " $(HEADERS)\n\t$(CC) $(CFLAGS) " $1 "\n"; }' | sed -e 's/.cpp././' | sed -e 's/\.cxx\./\./' | sed -e 's?/%FP%/?/?' >> MakeF
echo>> MakeF #EOF
mv MakeF Makefile


genmake-for-lib.dll component-name [path-to-sources [target-directory]]
@echo off
rem genmake component-name [path-to-sources [target-directory]]
set CN=%1
set FP=%2
if -%1==- set CN=default
if -%2==- set FP=.
if -%3==- set TD=.
echo>  MakeF COMPONENT=%CN%
echo>> MakeF include ../make/platform.mak
echo>> MakeF TARGET=$(OBJDIR)/lib$(COMPONENT).$(LibSuffix)
echo>> MakeF OBJS=\
find %FP% -type f -name '*.c*' | grep -v .svn | awk '{ print "\t$(OBJDIR)/" $1 ".$(OBJSuffix)\t\\\"; }' | sed -e 's/\.cpp\./\./' | sed -e 's/\.cxx\./\./' | sed -e 's?/%FP%??' >> MakeF
echo>> MakeF #
echo>> MakeF HEADERS=\
find %FP% -type f -name '*.h*' | grep -v .svn | awk '{ print "\t" $1 "\t\\\"; }' > headers  >> MakeF
echo>> MakeF #
echo>> MakeF #
echo>> MakeF $(TARGET): $(OBJS)
echo>> MakeF 	$(AR) $(LFLAGS) $(OBJS)
echo>> MakeF #
echo>> MakeF clean:
echo>> MakeF 	rm -f $(TARGET) $(OBJS)
echo>> MakeF #
find %FP% -type f -name '*.c*' | grep -v .svn | awk '{ print "$(OBJDIR)/" $1 ".$(OBJSuffix) : " $1 " $(HEADERS)\n\t$(CC) $(CFLAGS) " $1 "\n"; }' | sed -e 's/.cpp././' | sed -e 's/\.cxx\./\./' | sed -e 's?/%FP%/?/?' >> MakeF
echo>> MakeF #EOF
mv MakeF Makefile


genmake-for-indd.bat plugin-name [path-to-sources [target-directory]]
@echo off
setlocal
rem genmake plugin-name [path-to-sources [target-directory]]
set CN=%1
set FP=%2
if -%1==- set CN=plugin
if -%2==- set FP=.
if -%3==- set TD=.
echo>  MakeF COMPONENT=%CN%
echo>> MakeF include ../make/platform.mak
echo>> MakeF PluginName=$(COMPONENT)
echo>> MakeF TARGET_DIR=$(OBJDIR)/$(PluginPrefix)
echo>> MakeF TARGET=$(TARGET_DIR)/$(PluginName)$(PluginSuffix)
echo>> MakeF CFLAGS+=-I ../common
echo>> MakeF LIBS+=$(call add_component_ref,vl) $(call add_component_ref,common)
echo>> MakeF OBJS=\
find %FP% -type f -name '*.c*' | grep -v .svn | awk '{ print "\t$(OBJDIR)/" $1 ".$(OBJSuffix)\t\\\"; }' | sed -e 's/\.cpp\./\./' | sed -e 's/\.cxx\./\./' | sed -e 's?/%FP%??' >> MakeF
echo>> MakeF     $(COMMON_PLUGIN_OBJS)
echo>> MakeF #
echo>> MakeF HEADERS=\
find %FP% -type f -name '*.h*' | grep -v .svn | awk '{ print "\t" $1 "\t\\\"; }' > headers  >> MakeF
echo>> MakeF #
echo>> MakeF #
echo>> MakeF ifeq (win,$(OS))
echo>> MakeF OBJS+= $(OBJDIR)/$(COMPONENT).res
echo>> MakeF FRES_TGT=$(OBJDIR)/$(COMPONENT)_w.$(FRES)
echo>> MakeF else
echo>> MakeF FRES_TGT=$(OBJDIR)/R/$(COMPONENT)_w.$(FRES)
echo>> MakeF endif
echo>> MakeF #
echo>> MakeF ifeq (mac,$(OS))
echo>> MakeF ifeq (x64,$(ARCH))
echo>> MakeF TARGET:=
echo>> MakeF endif
echo>> MakeF endif
echo>> MakeF #
echo>> MakeF all: $(TARGET)
echo>> MakeF #
echo>> MakeF clean:
echo>> MakeF 	rm -rf $(TARGET) $(OBJS) $(TARGET_DIR)/*
echo>> MakeF #
echo>> MakeF #
echo>> MakeF $(TARGET): $(OBJS) $(LIBS) $(FRES_TGT)
echo>> MakeF 	$(LINK) $(LDFLAGS) $(LDFLAGS_InDesignPlugin) $(OBJS) $(LIBS) $(AdobeLIBS) $(XLIBS)
echo>> MakeF #
echo>> MakeF #
echo>> MakeF ifeq ($(OS),win)
echo>> MakeF $(OBJDIR)/$(COMPONENT)_w.$(FRES): $(COMPONENT).fr
echo>> MakeF 	$(ODFRC) $(ODFRCFLAGS) -o $(call unix_to_dos,$(OBJDIR)/$(COMPONENT)_w.$(FRES)) $(COMPONENT).fr
echo>> MakeF #
echo>> MakeF $(OBJDIR)/$(COMPONENT).res: $(COMPONENT).rc $(OBJDIR)/$(COMPONENT)_w.$(FRES)
echo>> MakeF 	$(RSC)  $(RSCFLAGS) $(CFLAGS_INCLUDE) $(COMPONENT).rc
echo>> MakeF 	$(AdobeSDK)\devtools\bin\merge_res.cmd $(call unix_to_dos,$(OBJDIR)) $(COMPONENT) $(COMPONENT)_w
echo>> MakeF 	mkdir -p "$(TARGET_DIR)/($(PluginName) Resources)"
echo>> MakeF 	cp -r $(OBJDIR)/idrc_* "$(TARGET_DIR)/($(PluginName) Resources)"
echo>> MakeF endif
echo>> MakeF ifeq ($(OS),mac)
echo>> MakeF $(OBJDIR)/R/$(COMPONENT)_w.$(FRES): $(COMPONENT).fr
echo>> MakeF 	mkdir -p $(TARGET_DIR)/Resources
echo>> MakeF 	mkdir -p $(OBJDIR)/R
echo>> MakeF 	$(ODFRC) $(ODFRCFLAGS) $(CFLAGS_IXA_OF) -o $@ $(COMPONENT).fr
echo>> MakeF 	/Developer/Tools/ResMerger -dstIs DF $@ -o $(OBJDIR)/$(PluginName).$(FRES)
echo>> MakeF 	/Developer/Tools/ResMerger $(OBJDIR)/$(PluginName).$(FRES) -dstIs DF -o $(TARGET_DIR)/Resources/$(PluginName).rsrc
echo>> MakeF 	cp -r $(OBJDIR)/R/idrc_* Info.plist $(TARGET_DIR)/Resources
echo>> MakeF 	( cd $(TARGET_DIR)/..; ln -s A Current ; cd .. ; ln -s Versions/Current/Resources Resources ; ln -s Versions/Current/$(PluginName) $(PluginName) )
echo>> MakeF endif
echo>> MakeF include ../make/commplugs.mak
echo>> MakeF #
echo>> MakeF #
find %FP% -type f -name '*.c*' | grep -v .svn | awk '{ print "$(OBJDIR)/" $1 ".$(OBJSuffix) : " $1 " $(HEADERS)\n\t$(CC) $(CFLAGS) " $1 "\n"; }' | sed -e 's/.cpp././' | sed -e 's/\.cxx\./\./' | sed -e 's?/%FP%/?/?' >> MakeF
echo>> MakeF #EOF
rem mv MakeF Makefile
endlocal


image

Ничто не предвещало, и ага


И все это прекрасно работало до того момента, пока не вышел Adobe InDesign CC 2014. И захотел вижуалстудию 2012. Вот тут-то белый пушной зверь каак выпрыгнул!

Нет, я конечно в теории знал, что вижуалстудия давным-давно не работает на ХРюше. Но вот то что cl.exe внезапно оказался not valid Win32 image — это был удар.

Немного поясню — еще со времен двух вижуалстудий 2005 с сервиспаком и без сервиспака одновременно, на билд машинку я ничего честно не ставлю. Для этого есть чистая виртуалка, в которую ставлю вижуалстудию express edition, накатываю правильный platform sdk, и то что получилось (лицензионно чистое и так далее) копирую в соответствующую папочку. А виртиуалку откатываю обратно до состояния «ничего не было».

И раз инсталлятор 2012 студии захотел поновее — не вопрос, вот вам Windows 8.1. Любой каприз — для микрософт.

Ставлю, копирую — опаньки.

image

Месье знает толк в извращениях



И тут встал суровой силы вопрос — что делать?

Вариантов немного.

  1. Поставить и обжить новую виртуалку под Windows 8.1, начиная от MinGW и заканчивая индезигнами. И лицензии найти надо — они конечно у меня все есть, но лежат в совершенно разных местах. Долго и нудно.
  2. Перебраться в амазоново облако — на w2k12, хватит надолго и работать будет быстро. Но снова та же проблема — долго и нудно. И все эти накопившиеся версии и копии — 25 гигов перебрасывать. Лениво.
  3. Извернуться так чтобы не пришлось ничего менять.

Почесал я маковку, и подумал — а пуркуа бы не па? Ведь хостом у меня опенсусь стоит.

Набираю
wine где-там-скопировал-2012-студию\vc\bin\cl.exe /help

и оно таки работает.

— Ага! — сказали суровые сибирские мужики.

Не без граблей конечно прошло — выяснилось что некоторые библиотеки от вижуалстудийного рантайма в вайне не совсем такие. Это не проблема — у вижуалстудии есть эталонные, скопировал по папочкам вижуалстудии.

А вот что удивило, совершенно простой mt.exe не только сам падал, но и вызывал SIGSEGV у wine. Шаманство с библиотеками решения не дало, пришлось по-быстрому написать свой заменитель с поэтессами.

Скрытый текст
#include <windows.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <io.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

static void usage() {
	printf("Usage: mt.exe -manifest foo.dll.manifest -outputresource:foo.dll[;2]\n");
}

static void alert(char* fn, char* msg, int code) {
    static char* lpstrError;
    FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM |  FORMAT_MESSAGE_ALLOCATE_BUFFER,
         NULL, GetLastError(), LANG_USER_DEFAULT, &lpstrError, 1, NULL);
    fprintf(stderr, "%s: %s: %s, %d\n", fn, msg, lpstrError, code);
    LocalFree(lpstrError);
}

static int update_res(char* ou, int resk, char* mf) {
	HANDLE hUpdateRes;
	LPVOID buf;
	BOOL result;
	FILE* fp;
	struct _stat st;
	int ressz = 0, outk;

	fp = fopen(mf, "rb");
	if(!fp) {
		alert(mf, "could not open manifest file", errno);
		return 2;
	}

	if(_fstat( fileno(fp), &st) != 0) {
		fclose(fp);
		alert(mf, "could not determine manifest file size", errno);
		return 2;
	}

	ressz = st.st_size;
	buf = (void*)malloc(ressz);
	if(!buf) {
		fclose(fp);
		free(buf);
		alert(mf, "could not allocate buffer for resource", ressz);
		return 2;
	}
	
	outk = fread(buf, 1, ressz, fp);
	if(outk != ressz) {
		fclose(fp);
		free(buf);
		alert(mf, "could not read manifest", ressz - outk);
		return 2;
	}
	fclose(fp);

	hUpdateRes = BeginUpdateResourceA(ou, FALSE);
	if (hUpdateRes == NULL) {
		free(buf);
	    alert(ou, "Could not open file for writing.", 0);
    	return 3;
	}

	result = UpdateResourceA(hUpdateRes,
    	RT_MANIFEST,
    	MAKEINTRESOURCE(resk),
    	MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL),
    	buf,
    	ressz);

	if (result == FALSE) {
		alert(ou, "could not add resource", 0);
		free(buf);
    	return 4;
	}

	if (!EndUpdateResource(hUpdateRes, FALSE)) {
    	alert(ou, "could not write changes to file", 0);
		free(buf);
    	return 4;
	}
	free(buf);
    return 0;
}

int main(int argc, char** argv)
{
	char* mf = NULL;
	char* ou = NULL;
	char* v;
	int resk = 1;
	int k;

	if(argc < 2) {
		usage();
		return 2;
	}

	for(k=1; k < argc; ++k) {
		if(argv[k][0] == '-') {
			if(argv[k][1] =='m') { // manifest
				mf = argv[k+1];
				++k;
				continue;
			}
			else if(argv[k][1] == 'o' ) { // outputresource
				if(argv[k+1]) 
					ou = argv[k+1];
				else {
					ou = strchr(argv[k], ':');
					if(!ou) {
						usage();
						return 3;
					}
					++ou;
				}
				++k;
				v = strchr(ou, ';');
				if(v) {
					resk = atoi(v + 1);
					*v = '\0';
				}
				else
					resk = 1;
				continue;
			}
		}
		usage();
		return 2;
	}

	if(!mf || !ou) {
		usage();
		return 2;
	}

	return update_res(ou, resk, mf);
}


А вот InnoSetup против ожиданий — сюрпризов не принес, собирает со свистом.

Пока — так, а далее поеду в облако, момент назрел.

Но все же решил поделиться с сообществом — вдруг я чего упускаю, вдруг кому пригодится…
Only registered users can participate in poll. Log in, please.
Стоила ли овчинка выделки?
18.52% надо было давно перейти на Windows 8, чего автор слоупочит — непонятно85
27.23% давно надо было все перенести на Linux125
27.23% ну вы там и курите ;-)125
26.36% однако, wine — отличная вещь, раз целая ферма из разных версий вижуалстудий в ней работает121
0.65% свой вариант3
459 users voted. 128 users abstained.
Tags:
Hubs:
+24
Comments32

Articles