¿Cómo puedo gestionar mejor la publicación de código fuente abierto a partir del código de investigación confidencial de mi empresa?

13

Mi compañía (llamémosles Acme Technology) tiene una biblioteca de aproximadamente mil archivos fuente que originalmente provino de su grupo de investigación Acme Labs, incubados en un grupo de desarrollo durante un par de años, y recientemente se han proporcionado a un puñado de clientes bajo no divulgación Acme se está preparando para lanzar quizás el 75% del código a la comunidad de código abierto. El otro 25% se lanzaría más tarde, pero por ahora, no está listo para el uso del cliente o contiene código relacionado con las innovaciones futuras que deben mantener fuera del alcance de los competidores.

El código está formateado actualmente con #ifdefs que permiten que la misma base de código trabaje con las plataformas de preproducción que estarán disponibles para los investigadores universitarios y una gama mucho más amplia de clientes comerciales una vez que vaya a código abierto, mientras que al mismo tiempo disponible para experimentación y creación de prototipos y pruebas de compatibilidad con la futura plataforma. Mantener una sola base de código se considera esencial para la economía (y la cordura) de mi grupo, que tendría dificultades para mantener dos copias en paralelo.

Los archivos en nuestra base actual se ven más o menos así:

> // 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
> }

Y nos gustaría convertirlos en algo como:

> // 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);
> }

¿Existe una herramienta, una biblioteca de análisis o un script popular que pueda reemplazar el copyright y eliminar no solo #ifdefs, sino variaciones como #if definido (UNDER_RESEARCH), etc.?

El código está actualmente en Git y probablemente estaría alojado en algún lugar que use Git. ¿Habría una manera de vincular los repositorios de manera segura para que podamos reintegrar de manera eficiente nuestras mejoras con las versiones de código abierto? Cualquier consejo sobre otras trampas es bienvenido.

DesarrolladorDon
fuente
13
Esta base de código está gritando por ramas.
Florian Margaine el
Sería bienvenido un ejemplo de uso de ramas para este propósito.
DesarrolladorDon

Respuestas:

6

Parece que no sería demasiado difícil escribir un guión para analizar los preprocesadores, ellos se compara a una lista de constantes definidas ( UNDER_RESEARCH, FUTURE_DEVELOPMENT, etc.) y, si la directiva puede ser evaluada a falso teniendo en cuenta lo que está definido, quitar todo lo que hasta a la siguiente #endif.

En Python, haría algo como,

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')

Estoy seguro de que hay formas más elegantes de hacerlo, pero esto es rápido y sucio y parece hacer el trabajo.

WasabiFlux
fuente
Wow gracias. Potencialmente, hay mucha lógica para hacer un buen filtro y agradezco su ejemplo. Espero encontrar algo para reutilizar, y mi máquina de desarrollo es rápida con una gran memoria, por lo que el rendimiento no es una gran preocupación para ejecutar filtros separados para los derechos de autor y las definiciones, o para ejecutar el filtro de definición más de una vez. En realidad, tenemos múltiples definiciones relacionadas con palabras clave que designan múltiples proyectos futuros y un par de proyectos pasados ​​que no se lanzarán de código abierto, pero que aún se utilizan internamente y al adoptar a los clientes de manera temprana.
DesarrolladorDon
3

Estaba pensando en pasar su código a través del preprocesador para expandir solo las macros, y así generar solo la parte interesante en el #ifdefs.

Algo como esto debería funcionar:

gcc -E yourfile.c

Pero:

  • Perderás todos los comentarios. Puede usarlos -CCpara preservarlos, pero aún así tendrá que quitar el aviso de copyright anterior
  • #includes también se expanden, por lo que terminará con un archivo grande que contiene todo el contenido de los archivos de encabezado incluidos
  • Perderá las macros "estándar".

Puede haber una forma de limitar qué macros se expanden; Sin embargo, mi sugerencia aquí es dividir las cosas, en lugar de hacer un procesamiento (potencialmente peligroso) en los archivos (por cierto, ¿cómo planeas mantenerlos después?

Es decir, intente poner el código en el que desea abrir código fuente en bibliotecas externas tanto como sea posible, luego úselo como lo haría con cualquier otra biblioteca, integrándolo con otras bibliotecas de código cerrado "personalizadas".

Al principio puede llevar un poco más de tiempo descubrir cómo reestructurar las cosas, pero definitivamente es la forma correcta de lograrlo.

sombra roja
fuente
Había considerado si podría hacerse algo con el preprocesador para eliminar selectivamente los bloques que aún no liberaremos. El código es complejo y probablemente necesitemos más comentarios en lugar de menos, pero vale la pena tener su sugerencia en la lista de lluvia de ideas. Las preguntas de WRT sobre cómo planeamos mantener la fuente y mover el código hacia atrás y hacia adelante a la comunidad, se necesita más planificación. Traer código al código propietario plantea algunas buenas preguntas.
DesarrolladorDon
2

Tengo una solución pero requerirá un poco de trabajo.

pypreprocessor es una biblioteca que proporciona un preprocesador puro de estilo c para python que también se puede usar como GPP (preprocesador de uso general) para otros tipos de código fuente.

Aquí hay un ejemplo básico:

from pypreprocessor import pypreprocessor

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

El preprocesador es extremadamente simple. Hace un pase a través de la fuente y condicionalmente comenta la fuente en función de lo que está definido.

Las definiciones se pueden establecer a través de #define declaraciones en la fuente o configurándolas en la lista pypreprocessor.defines.

Establecer los parámetros de entrada / salida le permite definir explícitamente qué archivos se están abriendo / cerrando para que se pueda configurar un solo preprocesador para procesar por lotes una gran cantidad de archivos si lo desea.

Al establecer el parámetro removeMeta en True, el preprocesador debe extraer automáticamente todas y cada una de las declaraciones del preprocesador, dejando solo el código procesado posteriormente.

Nota: Por lo general, esto no necesitaría establecerse explícitamente porque Python eliminó el código comentado automáticamente durante la compilación a bytecode.

Solo veo un caso de borde. Debido a que está buscando preprocesar la fuente C, es posible que desee establecer las definiciones del procesador explícitamente (es decir, a través de pypreprocessor.defines) y decirle que ignore las declaraciones #define en la fuente. Eso debería evitar que elimine accidentalmente las constantes que pueda usar en el código fuente de su proyecto. Actualmente no hay ningún parámetro para establecer esta funcionalidad, pero sería trivial agregarlo.

Aquí hay un ejemplo trivial:

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()

Entonces la fuente:

#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

Nota: Obviamente, necesitará resolver una forma de configurar los archivos de entrada / salida, pero eso no debería ser demasiado difícil.

Divulgación: Soy el autor original de pypreprocessor.


Aparte: originalmente lo escribí como una solución al temido problema de mantenimiento de python 2k / 3x. Mi enfoque fue hacer 2 y 3 desarrollos en los mismos archivos fuente y solo incluir / excluir las diferencias usando directivas de preprocesador. Desafortunadamente, descubrí de la manera difícil que es imposible escribir un preprocesador puro puro (es decir, no requiere c) en python porque el lexer marca errores de sintaxis en código incompatible antes de que el preprocesador tenga la oportunidad de ejecutarse. De cualquier manera, sigue siendo útil en una amplia gama de circunstancias, incluida la suya.

Evan Plaice
fuente
Esto mola. Si nada más podríamos hacer algo así como un diff de tres vías que procesó los archivos con y sin el código que queríamos excluir, tomó su diff, luego eliminó las líneas diffed del original.
DesarrolladorDon
@DeveloperDon Sí, esa es la idea general. Hay algunas formas diferentes de manejarlo, depende de cómo planee administrar el ciclo de confirmación de lanzamiento. Esta pieza simplemente automatiza gran parte del trabajo que de otro modo sería tedioso y / o propenso a errores.
Evan Plaice el
1

Probablemente sería buena idea

1.Agregue etiquetas de comentarios como:

> // *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. Escriba un script para que el generador de código abierto revise todos los archivos y reemplace el texto entre las etiquetas COPYRIGHT-BEGIN-TAG y COPYRIGHT-ENG-TAG

Alex Hashimi
fuente
1
¿Necesito la etiqueta de inicio? Hasta ahora, todos nuestros archivos fuente comienzan con los derechos de autor en la primera línea, y nuestros scripts de shell comienzan con los derechos de autor en la segunda línea. Hay muchos archivos, por lo que me gustaría realizar la menor cantidad de edición manual posible.
DesarrolladorDon
Creo que algunos archivos pueden usar Doxygen para delinear sus nombres de función, parámetro y valor de retorno. Para aquellos archivos que aún no están configurados de esa manera, realmente podría ser mucha edición si tomamos una decisión que tome más en esa dirección.
DesarrolladorDon
Al menos tienes que cambiarlo una vez. Si su política de derechos de autor cambió, puede administrarla.
Alex Hashimi
1

No voy a mostrarle una herramienta para convertir su base de código, muchas respuestas ya lo hicieron. Más bien, estoy respondiendo tu comentario sobre cómo manejar ramas para esto.

Debes tener 2 ramas:

  • Comunidad (llamemos a la versión de código abierto como esta)
  • Profesional (llamemos a la versión de código cerrado como esta)

Los preprocesadores no deberían existir. Tienes dos versiones diferentes. Y una base de código más limpia en general.

¿Tienes miedo de mantener dos copias en paralelo? ¡No te preocupes, puedes unirte!

Si está realizando modificaciones en la rama de la comunidad, simplemente combínelas en la rama profesional. Git maneja esto muy bien.

De esta manera, conserva 2 copias mantenidas de su base de código. Y lanzar uno para código abierto es fácil como un pastel.

Florian Margaine
fuente