Jak najlepiej zarządzać tworzeniem wydań kodu open source z poufnego kodu badawczego mojej firmy?

13

Moja firma (nazwijmy je Acme Technology) ma bibliotekę około tysiąca plików źródłowych, które pierwotnie pochodziły od jej grupy badawczej Acme Labs, inkubowanej przez kilka lat w grupie programistów, a ostatnio udostępniono garstce klientów pod nieujawnianie. Acme przygotowuje się do wydania około 75% kodu społeczności open source. Pozostałe 25% zostanie wydanych później, ale na razie albo nie jest gotowy do użytku przez klienta, albo zawiera kod związany z przyszłymi innowacjami, których muszą trzymać z dala od konkurentów.

Kod jest obecnie sformatowany za pomocą #ifdefs, które pozwalają tej samej bazie kodu współpracować z platformami przedprodukcyjnymi, które będą dostępne dla badaczy uniwersyteckich i znacznie szerszego grona klientów komercyjnych, kiedy przejdą na open source, jednocześnie będąc dostępne do eksperymentów i prototypowania oraz do testowania zgodności z przyszłą platformą. Utrzymanie jednej bazy kodu jest uważane za niezbędne dla ekonomii (i rozsądku) mojej grupy, która miałaby trudności z utrzymaniem dwóch kopii równolegle.

Pliki w naszej obecnej bazie wyglądają mniej więcej tak:

> // Copyright 2012 (C) Acme Technology, All Rights Reserved.
> // Very large, often varied and restrictive copyright license in English and French,
> // sometimes also embedded in make files and shell scripts with varied 
> // comment styles. 
> 
> 
>   ... Usual header stuff...
>
> void initTechnologyLibrary() {
>     nuiInterface(on);
> #ifdef  UNDER_RESEARCH
>     holographicVisualization(on);
> #endif
> }

I chcielibyśmy przekonwertować je na coś takiego:

> // GPL Copyright (C) Acme Technology Labs 2012, Some rights reserved.
> // Acme appreciates your interest in its technology, please contact [email protected] 
> // for technical support, and www.acme.com/emergingTech for updates and RSS feed.
> 
>   ... Usual header stuff...
>
> void initTechnologyLibrary() {
>     nuiInterface(on);
> }

Czy istnieje narzędzie, biblioteka do analizy składni lub popularny skrypt, który może zastąpić prawa autorskie i usunąć nie tylko #ifdefs, ale także warianty takie jak #if zdefiniowane (UNDER_RESEARCH) itp.?

Kod jest obecnie w Git i prawdopodobnie byłby przechowywany gdzieś, gdzie używa Git. Czy istnieje sposób bezpiecznego łączenia repozytoriów ze sobą, abyśmy mogli skutecznie zintegrować nasze ulepszenia z wersjami open source? Porady na temat innych pułapek są mile widziane.

DeveloperDon
źródło
13
Ta baza kodów krzyczy o gałęzie.
Florian Margaine,
Przykład wykorzystania oddziałów w tym celu byłby bardzo mile widziany.
DeveloperDon

Odpowiedzi:

6

Wydaje się, że to nie byłoby zbyt trudne do napisania skryptu do analizowania preprocesory, porównać je do listy zdefiniowanych stałych ( UNDER_RESEARCH, FUTURE_DEVELOPMENTetc.), a jeżeli dyrektywa może być oceniana na fałszywym danym co zdefiniowany, usuń wszystko w górę do następnego #endif.

W Pythonie zrobiłbym coś takiego,

import os

src_dir = 'src/'
switches = {'UNDER_RESEARCH': True, 'OPEN_SOURCE': False}
new_header = """// GPL Copyright (C) Acme Technology Labs 2012, Some rights reserved.
// Acme appreciates your interest in its technology, please contact [email protected] 
// for technical support, and www.acme.com/emergingTech for updates and RSS feed.
"""

filenames = os.listdir(src_dir)
for fn in filenames:
    contents = open(src_dir+fn, 'r').read().split('\n')
    outfile = open(src_dir+fn+'-open-source', 'w')
    in_header = True
    skipping = False
    for line in contents:
        # remove original header
        if in_header and (line.strip() == "" or line.strip().startswith('//')):
            continue
        elif in_header:
            in_header = False
            outfile.write(new_header)

        # skip between ifdef directives
        if skipping:
            if line.strip() == "#endif":
                skipping = False
            continue
        # check
        if line.strip().startswith("#ifdef"):
            # parse #ifdef (maybe should be more elegant)
            # this assumes a form of "#ifdef SWITCH" and nothing else
            if line.strip().split()[1] in switches.keys():
                skipping = True
                continue

        # checking for other forms of directives is left as an exercise

        # got this far, nothing special - echo the line
        outfile.write(line)
        outfile.write('\n')

Jestem pewien, że istnieją bardziej eleganckie sposoby na zrobienie tego, ale jest to szybkie i brudne i wydaje się, że praca jest wykonana.

WasabiFlux
źródło
Wow, dzięki. Potrzeba wiele logiki, aby stworzyć dobry filtr i doceniam twój przykład. Mam nadzieję, że znajdę coś do ponownego użycia, a moja maszyna programistyczna jest szybka z dużą pamięcią, więc wydajność nie jest wielkim problemem, aby uruchamiać osobne filtry dla praw autorskich i definicji lub uruchamiać filtr definicyjny więcej niż raz. W rzeczywistości mamy wiele definicji związanych ze słowami kluczowymi, które oznaczają wiele przyszłych projektów i kilka poprzednich projektów, które nie zostaną wydane jako oprogramowanie open source, ale nadal są używane wewnętrznie i przez wczesne przyjmowanie klientów.
DeveloperDon
3

Myślałem o przekazaniu kodu przez preprocesor, aby rozwinąć tylko makra, a tym samym wypisać tylko interesującą część w #ifdefs.

Coś takiego powinno działać:

gcc -E yourfile.c

Ale:

  • Stracisz wszystkie komentarze. Możesz użyć, -CCaby (w pewnym sensie) je zachować, ale nadal będziesz musiał usunąć starą informację o prawach autorskich
  • #includes są również rozwinięte, więc powstanie duży plik zawierający całą zawartość dołączonych plików nagłówkowych
  • Utracisz „standardowe” makra.

Może istnieć sposób ograniczenia, które makra są rozwijane; jednakże proponuję tutaj podzielić rzeczy, zamiast robić (potencjalnie niebezpieczne) przetwarzanie plików (tak przy okazji, jak planowałbyś je później utrzymywać? np. przywrócić kod z wersji opensource do twojego zamkniętego źródła?).

Innymi słowy, spróbuj umieścić kod, który chcesz otworzyć, w zewnętrznych bibliotekach tak często, jak to możliwe, a następnie użyj ich tak, jak w każdej innej bibliotece, integrując się z innymi „niestandardowymi” bibliotekami o zamkniętym źródle.

Na początku może zająć trochę więcej czasu, aby dowiedzieć się, jak zrestrukturyzować rzeczy, ale jest to zdecydowanie właściwy sposób na osiągnięcie tego.

redShadow
źródło
Zastanawiałem się, czy można coś zrobić z preprocesorem, aby selektywnie wyeliminować bloki, których jeszcze nie wydamy. Kod jest złożony i prawdopodobnie potrzebujemy więcej komentarzy niż mniej, ale twoja sugestia z pewnością jest warta umieszczenia na liście burzy mózgów. Pytania WRT o tym, w jaki sposób planujemy utrzymywać kod źródłowy i przenosić kod wstecz i dalej do społeczności, potrzebne jest więcej planowania. Przeniesienie kodu do zastrzeżonego kodu rodzi kilka dobrych pytań.
DeveloperDon
2

Mam rozwiązanie, ale będzie wymagało trochę pracy

pypreprocessor to biblioteka, która zapewnia preprocesor w stylu c dla Pythona, który może być również używany jako GPP (Preprocesor ogólnego przeznaczenia) dla innych typów kodu źródłowego.

Oto podstawowy przykład:

from pypreprocessor import pypreprocessor

pypreprocessor.input = 'input_file.c'
pypreprocessor.output = 'output_file.c'
pypreprocessor.removeMeta = True
pypreprocessor.parse()

Preprocesor jest niezwykle prosty. Dokonuje przejścia przez źródło i warunkowo komentuje źródło na podstawie tego, co jest zdefiniowane.

Definicje można ustawić za pomocą instrukcji #define w źródle lub ustawiając je na liście pypreprocessor.defines.

Ustawienie parametrów wejściowych / wyjściowych pozwala jawnie zdefiniować, które pliki są otwierane / zamykane, dzięki czemu pojedynczy preprocesor może zostać skonfigurowany do przetwarzania wsadowego dużej liczby plików w razie potrzeby.

Ustawiając parametr removeMeta na wartość True, preprocesor powinien automatycznie wyodrębnić wszystkie instrukcje preprocesora, pozostawiając jedynie przetworzony kod.

Uwaga: Zwykle nie trzeba tego ustawiać jawnie, ponieważ Python automatycznie usuwa kod komentarza podczas kompilacji do kodu bajtowego.

Widzę tylko jedną obudowę krawędzi. Ponieważ chcesz wstępnie przetworzyć źródło C, możesz chcieć ustawić definicje procesora jawnie (tj. Poprzez pypreprocessor.defines) i powiedzieć mu, aby ignorował instrukcje #define w źródle. Powinno to uchronić go przed przypadkowym usunięciem wszelkich stałych, których możesz użyć w kodzie źródłowym swojego projektu. Obecnie nie ma parametru, który ustawiałby tę funkcjonalność, ale dodanie tego byłoby trywialne.

Oto prosty przykład:

from pypreprocessor import pypreprocessor

# run the script in 'production' mode
if 'commercial' in sys.argv:
    pypreprocessor.defines.append('commercial')

if 'open' in sys.argv:
    pypreprocessor.defines.append('open')

pypreprocessor.removeMeta = True
pypreprocessor.parse()

Następnie źródło:

#ifdef commercial
// Copyright 2012 (C) Acme Technology, All Rights Reserved.
// Very large, often varied and restrictive copyright license in English and French,
// sometimes also embedded in make files and shell scripts with varied 
// comment styles.
#ifdef open
// GPL Copyright (C) Acme Technology Labs 2012, Some rights reserved.
// Acme appreciates your interest in its technology, please contact [email protected] 
// for technical support, and www.acme.com/emergingTech for updates and RSS feed.
#endif

Uwaga: Oczywiście musisz znaleźć sposób ustawienia plików wejściowych / wyjściowych, ale nie powinno to być zbyt trudne.

Ujawnienie: Jestem oryginalnym autorem pypreprocesora.


Poza tym: pierwotnie napisałem to jako rozwiązanie problemu związanego z konserwacją Pythona 2k / 3x. Moje podejście polegało na zrobieniu programowania 2 i 3 w tych samych plikach źródłowych i po prostu uwzględnianie / wykluczanie różnic za pomocą dyrektyw preprocesora. Niestety odkryłem, że nie da się napisać prawdziwego czystego (tj. Nie wymaga c) preprocesora w pythonie, ponieważ lexer zaznacza błędy składniowe w niekompatybilnym kodzie, zanim preprocesor będzie miał szansę na uruchomienie. Tak czy inaczej, nadal jest przydatny w wielu okolicznościach, w tym w twojej.

Evan Plaice
źródło
To kołysze. Jeśli nic więcej nie moglibyśmy zrobić z trójdrożnym diff, który przetwarzał pliki z kodem i bez kodu, który chcieliśmy wykluczyć, wziął ich różnicę, a następnie usunął linie różnicowe z oryginału.
DeveloperDon
@DeveloperDon Yep, to jest ogólny pomysł. Istnieje kilka różnych sposobów radzenia sobie z tym, zależy to od tego, jak planujesz zarządzać cyklem wydawania zmian. Ten kawałek po prostu automatyzuje wiele prac, które w innym przypadku byłyby uciążliwe i / lub podatne na błędy.
Evan Plaice
1

Prawdopodobnie byłby to dobry pomysł

1. dodaj tagi komentarza, takie jak:

> // *COPYRIGHT-BEGIN-TAG*
> // Copyright 2012 (C) Acme Technology, All Rights Reserved.
> // Very large, often varied and restrictive copyright license in English and French,
> // sometimes also embedded in make files and shell scripts with varied 
> // comment styles. 
> // *COPYRIGHT-ENG-TAG*
>   ... Usual header stuff...
>
> void initTechnologyLibrary() {
>     nuiInterface(on);
> #ifdef  UNDER_RESEARCH
>     holographicVisualization(on);
> #endif
> }

2. Napisz skrypt dla konstruktora oprogramowania open source, aby przeglądał wszystkie pliki i zamieniał tekst między znacznikami COPYRIGHT-BEGIN-TAG i COPYRIGHT-ENG-TAG

Alex Hashimi
źródło
1
Czy potrzebuję tagu start? Jak dotąd wszystkie nasze pliki źródłowe zaczynają się od praw autorskich w pierwszym wierszu, a nasze skrypty powłoki zaczynają od praw autorskich w drugim wierszu. Istnieje wiele plików, więc chciałbym wykonać jak najmniejszą możliwą edycję ręczną.
DeveloperDon
Myślę, że niektóre pliki mogą używać Doxygen do określenia ich funkcji, parametrów i nazw wartości zwrotnych. W przypadku plików, które nie zostały jeszcze skonfigurowane w ten sposób, może być naprawdę dużo edycji, jeśli dokonamy wyboru, który podjąłby się w tym kierunku.
DeveloperDon
Przynajmniej musisz to raz zmienić. jeśli zmieniła się twoja polityka praw autorskich, możesz nią zarządzać.
Alex Hashimi,
1

Nie zamierzam pokazywać narzędzia do konwersji bazy kodu, wiele odpowiedzi już to zrobiło. Odpowiadam raczej na twój komentarz na temat obsługi gałęzi w tym celu.

Powinieneś mieć 2 oddziały:

  • Społeczność (nazwijmy taką wersję open source)
  • Professional (nazwijmy taką wersję zamkniętego źródła)

Preprocesory nie powinny istnieć. Masz dwie różne wersje. I ogólnie czystsza baza kodów.

Boisz się równoległego przechowywania dwóch kopii? Nie martw się, możesz się połączyć!

Jeśli dokonujesz modyfikacji w oddziale społeczności, po prostu połącz je w oddziale profesjonalnym. Git radzi sobie z tym naprawdę dobrze.

W ten sposób przechowujesz 2 zachowane kopie swojej bazy kodu. Wydanie jednego dla Open Source jest łatwe jak ciasto.

Florian Margaine
źródło